module nade.physics.world;

import derelict.newton.newton;

import nade.vector;
import nade.array;

import nade.physics.rigid_body;


class World : IRenderer {
	struct TraceResult {
		bool		hit = false;
		double		hitFraction = 1;
		Vector3		hitPos;
		double		hitDist;
		Vector3		hitNormal;
		RigidBody	hitBody = null;
	}

	this()
	{
		_phys = NewtonCreate(null, null);
		NewtonWorldSetUserData(_phys, cast(void*)this);
		NewtonSetSolverModel(_phys, 1);
		NewtonSetFrictionModel(_phys, 0);
		NewtonSetBodyLeaveWorldEvent(_phys, &cbBodyLeftWorld);
	}
	~this()
	{
		NewtonDestroy(_phys);
	}

	void render()
	{
		foreach(obj; _objects)
			obj.render;
	}

	void update(double time)
	{
		NewtonUpdate(_phys, time);
	}

	void add(WorldObject obj)
	{
		_objects ~= obj;
	}
	void remove(WorldObject obj)
	{
		.remove(_objects, obj);
	}

	void clear()
	{
		NewtonDestroyAllBodies(_phys);
	}

	TraceResult traceRay(const Vector3 from, const Vector3 to)
	{
		TraceResult tr;
		tr.hitNormal = (to - from).normal;

		NewtonWorldRayCast(_phys, from.toArray!(double).ptr, to.toArray!(double).ptr, &cbRayFilter, cast(void*)&tr);

		tr.hitPos = from + (to - from)*tr.hitFraction;
		tr.hitDist = (to - from).modulus()*tr.hitFraction;

		return tr;
	}

	uint objectCount() { return _objects.length; }

	NewtonWorld* phys() { return _phys; }

	private {
		NewtonWorld*	_phys;

		WorldObject[]	_objects;
	}

	static World get(NewtonWorld* physWorld)
	{
		return cast(World)NewtonWorldGetUserData(physWorld);
	}

	static extern(C) double cbRayFilter(const NewtonBody* physBody, const double* hitNormal, int cid, void* userData, double fraction)
	{
		TraceResult* tr = cast(TraceResult*)userData;
		tr.hit = true;
		tr.hitFraction = fraction;
		tr.hitBody = RigidBody.get(physBody);
		tr.hitNormal = Vector3(hitNormal[0], hitNormal[1], hitNormal[2]);

		return fraction;
	}

	static extern(C) void cbBodyLeftWorld(const NewtonBody* physBody)
	{
		auto rigidBody = RigidBody.get(physBody);
		assert(rigidBody !is null);
		delete rigidBody;
	}
}

alias World.TraceResult TraceResult;


class WorldObject {
	this(World world)
	{
		_world = world;
	}

	void render()
	{
	}

	World world() { return _world; }

	private {
		World		_world;
	}
}
